//! CMS 管理的接口
use ane_cms::dao::{ArticleDetailVo, ArticleListVo, CategoryDetailVo, CategoryDo};
use ane_cms::dao::{ArticleDo, Manager as CmsManager};
use ane_system::dao::user;
use axum::routing::{get, post, put};
use axum::{
    extract::{Path, Query, State},
    response::IntoResponse,
    Extension,
};
use common::page::{PageInfo, PageParams};
use gen_table::{JsonMap, JsonValue};
use loginmanager::AuthUser;
use sea_orm::{EntityTrait, QueryFilter};
use serde::Deserialize;

use crate::security::LoginUser;
use crate::{
    error::AppResult,
    extract::{Json, ValJson},
    state::AppState,
};

/// ## CMS 管理的接口
///
/// - `{prefix}/article/`                 文章列出 新建
/// - `{prefix}/article/publish/:id`      文章发布
/// - `{prefix}/article/unpublish/:id`    文章取消发布
/// - `{prefix}/article/:id`              文章获取 更新 删除
/// - `{prefix}/article/:id/recycle`      文章放入回收站
/// - `{prefix}/article/:id/restore`      文章恢复
/// - `{prefix}/article/:id/publish`      文章发布或投稿
/// - `{prefix}/category/`                分类列出 新建
/// - `{prefix}/category/:id`             分类获取 更新 删除
///
/// ## 请求的资源
/// - `{prefix}/article/\d*(/(recycle|restore|publish))?$`

pub fn get_route(prefix: &str) -> axum::Router<AppState> {
    let f = move |p: &str| -> String { format!("{}{p}", prefix) };
    #[cfg_attr(rustfmt, rustfmt_skip)]
    axum::Router::new()
        .route(&f("/article/"), get(article_list).post(create_article))
        .route(&f("/article/:id"),get(fetch_article).put(update_article).delete(delete_article),)
        .route(&f("/article/:id/recycle"),put(recycle_article))
        .route(&f("/article/:id/restore"),put(restore_article))
        .route(&f("/article/:id/publish"),put(publish_article))
        .route(&f("/article/publish/:id"), put(publish_article))
        .route(&f("/article/unpublish/:id"), put(unpublish_article))
        .route(&f("/category/"), get(list_category).post(create_category))
        .route(&f("/category/:id"),get(fetch_category).put(update_category).delete(delete_category) )
}

#[derive(Deserialize)]
pub struct ListFilter {
    status: Option<i8>,
    deleted: Option<bool>,
}

pub async fn article_list(
    State(state): State<AppState>,
    Query(page_params): Query<PageParams>,
    Query(list_filter): Query<ListFilter>,
    AuthUser(user): AuthUser<LoginUser>,
) -> AppResult<impl IntoResponse> {
    let user_id = if user.data_scope() == 1 {
        None
    } else {
        Some(user.id)
    };
    if matches!(list_filter.deleted, Some(true)) {
        CmsManager::list_article_recycle::<ArticleListVo>(
            state.db(),
            page_params.zero_page(),
            page_params.page_size(),
            user_id,
            None,
            None,
        )
        .await
        .map(|(datas, total)| PageInfo::new(datas, &page_params, total))
        .map(Json)
        .map_err(Into::into)
    } else {
        CmsManager::list_article::<ArticleListVo>(
            state.db(),
            page_params.zero_page(),
            page_params.page_size(),
            user_id,
            None,
            None,
            list_filter.status,
        )
        .await
        .map(|(datas, total)| PageInfo::new(datas, &page_params, total))
        .map(Json)
        .map_err(Into::into)
    }
}

macro_rules! mmap {
    () => {
        serde_json::Map::<String, serde_json::Value>::new()
    };
    ($($last:tt),*) => {{
        let mut mm = mmap!();
        mm
    }};
}

fn mmap<T, T2>(d: T, key: String, v: T2) -> Result<serde_json::Value, serde_json::Error>
where
    T: serde::Serialize,
    T2: serde::Serialize,
{
    let mut s = serde_json::to_value(d)?;
    if let Some(sd) = s.as_object_mut() {
        let v = serde_json::to_value(v)?;
        sd.insert(key, v);
    }
    Ok(s)
}

pub async fn fetch_article(
    State(state): State<AppState>,
    Path(id): Path<i32>,
    AuthUser(user): AuthUser<LoginUser>,
) -> AppResult<impl IntoResponse> {
    let s = CmsManager::fetch_article::<ArticleDetailVo>(state.db(), Some(user.id), id).await?;
    let categories = if let Some(m) = &s {
        CmsManager::fetch_article_category_id(state.db(), m.id, None).await?
    } else {
        vec![]
    };
    Ok(Json(mmap(s, "categories".to_string(), categories)?))
}

pub async fn create_article(
    State(state): State<AppState>,
    AuthUser(user): AuthUser<LoginUser>,
    ValJson(mut data): ValJson<ArticleDo>,
) -> AppResult<impl IntoResponse> {
    // 创建和修改文章不能改变发布状态，方便以后做权限控制
    data.status = None;
    if data.author.is_none() {
        data.author = Some(user.nickname);
    }
    CmsManager::create_article(state.db(), user.id, data)
        .await
        .map(Json)
        .map_err(Into::into)
}

pub async fn update_article(
    State(state): State<AppState>,
    Path(id): Path<i32>,
    AuthUser(user): AuthUser<LoginUser>,
    ValJson(mut data): ValJson<ArticleDo>,
) -> AppResult<impl IntoResponse> {
    // 创建和修改文章不能改变发布状态，方便以后做权限控制
    data.status = None;
    CmsManager::update_article(state.db(), Some(user.id), id, data)
        .await
        .map(Json)
        .map_err(Into::into)
}

pub async fn publish_article(
    State(state): State<AppState>,
    Path(id): Path<i32>,
    AuthUser(user): AuthUser<LoginUser>,
) -> AppResult<impl IntoResponse> {
    /// 如果有可以审核稿件的角色，通过审核
    let status = if user.has_role("cms_reviewer")
        || user.has_role("cms_editor")
        || user.has_role("cms_admin")
    {
        2
    } else {
        1
    };
    let data = ArticleDo {
        status: Some(status),
        ..Default::default()
    };
    user.data_scope();

    CmsManager::update_article(state.db(), Some(user.id), id, data)
        .await
        .map(Json)
        .map_err(Into::into)
}

pub async fn unpublish_article(
    State(state): State<AppState>,
    Path(id): Path<i32>,
    AuthUser(user): AuthUser<LoginUser>,
) -> AppResult<impl IntoResponse> {
    let data = ArticleDo {
        status: Some(0),
        ..Default::default()
    };
    CmsManager::update_article(state.db(), Some(user.id), id, data)
        .await
        .map(Json)
        .map_err(Into::into)
}

pub async fn recycle_article(
    State(state): State<AppState>,
    Path(id): Path<i32>,
    AuthUser(user): AuthUser<LoginUser>,
) -> AppResult<impl IntoResponse> {
    use ane_cms::entity::cms_article::{self, Column, Entity};
    use sea_orm::prelude::*;
    // CmsManager::fetch_article(state.db(), Some(user.id), id).await;
    let r = Entity::update_many()
        .col_expr(Column::Status, Expr::col(Column::Status).add(64))
        .filter(Column::Id.eq(id))
        .filter(Column::Status.lt(64))
        .filter(Column::UserId.eq(user.id))
        .exec(state.db())
        .await?;
    return Ok(Json(r.rows_affected));
}

pub async fn restore_article(
    State(state): State<AppState>,
    Path(id): Path<i32>,
    AuthUser(user): AuthUser<LoginUser>,
) -> AppResult<impl IntoResponse> {
    use ane_cms::entity::cms_article::{self, Column, Entity};
    use sea_orm::prelude::*;
    // CmsManager::fetch_article(state.db(), Some(user.id), id).await;
    let r = Entity::update_many()
        .col_expr(Column::Status, Expr::col(Column::Status).sub(64))
        .filter(Column::Id.eq(id))
        .filter(Column::Status.gte(64))
        .filter(Column::UserId.eq(user.id))
        .exec(state.db())
        .await?;
    return Ok(Json(r.rows_affected));
}

pub async fn delete_article(
    State(state): State<AppState>,
    Path(id): Path<i32>,
    AuthUser(user): AuthUser<LoginUser>,
) -> AppResult<impl IntoResponse> {
    ane_cms::dao::Manager::del_article(state.db(), Some(user.id), id)
        .await
        .map(Json)
        .map_err(Into::into)
}

async fn list_category(State(state): State<AppState>) -> AppResult<impl IntoResponse> {
    ane_cms::dao::Cms::fetch_category(state.db(), None)
        .await
        .map(Json)
        .map_err(Into::into)
}

async fn fetch_category(
    State(state): State<AppState>,
    Path(id): Path<i32>,
) -> AppResult<impl IntoResponse> {
    ane_cms::dao::Manager::fetch_category::<CategoryDetailVo>(state.db(), id)
        .await
        .map(Json)
        .map_err(Into::into)
}

async fn update_category(
    State(state): State<AppState>,
    Path(id): Path<i32>,
    ValJson(data): ValJson<CategoryDo>,
) -> AppResult<impl IntoResponse> {
    ane_cms::dao::Manager::update_category(state.db(), id, data)
        .await
        .map(Json)
        .map_err(Into::into)
}

async fn delete_category(
    State(state): State<AppState>,
    Path(id): Path<i32>,
) -> AppResult<impl IntoResponse> {
    ane_cms::dao::Manager::del_category(state.db(), id)
        .await
        .map(Json)
        .map_err(Into::into)
}

async fn create_category(
    State(state): State<AppState>,
    ValJson(data): ValJson<CategoryDo>,
) -> AppResult<impl IntoResponse> {
    ane_cms::dao::Manager::add_category(state.db(), data)
        .await
        .map(Json)
        .map_err(Into::into)
}
